package com.tomorrow.runner.data;

import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;
import android.text.TextUtils;

import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;

import cn.sucun.android.log.Log;


/**
 * each BaseColums Object will always have _ID , _COUNT. this abstract class is
 * used to achieve the common use function。ContentValues 和HashTable类似都是一种存储的机制
 * but the big defference is
 * ，contenvalues只能存储基本类型的数据，像string，int之类的，不能存储对象这种东西，而HashTable却可以存储对象。
 */
public abstract class AbsData implements BaseColumns {
	private final String LOG_TAG = "AbsData";

	private final boolean mIgnoreNotExist;

	private final Set<String> COLUMNS_INT;
	private final Set<String> COLUMNS_LONG;
	private final Set<String> COLUMNS_STR;
	private final Set<String> COLUMNS_BOOL;
	private final Set<String> COLUMNS_DOUBLE;

	private final ContentValues mValues;
	private final ContentValues mChangedValues;

	protected AbsData(boolean ignoreNotExist, Set<String> stringColumns,
					  Set<String> intColumns, Set<String> longColumns,
					  Set<String> boolColumns,Set<String> doubleColumns) {
		mIgnoreNotExist = ignoreNotExist;

		COLUMNS_INT = intColumns;
		COLUMNS_LONG = longColumns;
		COLUMNS_STR = stringColumns;
		COLUMNS_BOOL = boolColumns;
		COLUMNS_DOUBLE = doubleColumns;

		mValues = new ContentValues();
		mChangedValues = new ContentValues();
	}

	protected AbsData(Cursor c, boolean ignoreNotExist,
					  Set<String> stringColumns, Set<String> intColumns,
					  Set<String> longColumns, Set<String> boolColumns,Set<String> doubleColumns) {
		this(ignoreNotExist, stringColumns, intColumns, longColumns,
				boolColumns,doubleColumns);
		init(c, ignoreNotExist);
	}

	/**
	 * put the key and value into values ContentValues from c
	 * 
	 * @param c
	 * @param ignoreNotExist
	 */
	private void init(Cursor c, boolean ignoreNotExist) {
		Set<String> stringColumns = COLUMNS_STR;
		Set<String> intColumns = COLUMNS_INT;
		Set<String> longColumns = COLUMNS_LONG;
		Set<String> boolColumns = COLUMNS_BOOL;
		Set<String> doubleColumns = COLUMNS_DOUBLE;

		int columnIndex;
		if (stringColumns != null) {
			for (String column : stringColumns) {
				columnIndex = c.getColumnIndex(column);
				if (columnIndex >= 0 || !ignoreNotExist) {
					mValues.put(column, c.getString(columnIndex));
				}
			}
		}

		if (longColumns != null) {
			for (String column : longColumns) {
				columnIndex = c.getColumnIndex(column);
				if (columnIndex >= 0 || !ignoreNotExist) {
					mValues.put(column, c.getLong(columnIndex));
				}
			}
		}

		if (intColumns != null) {
			for (String column : intColumns) {
				columnIndex = c.getColumnIndex(column);
				if (columnIndex >= 0 || !ignoreNotExist) {
					mValues.put(column, c.getInt(columnIndex));
				}
			}
		}

		if (boolColumns != null) {
			for (String column : boolColumns) {
				columnIndex = c.getColumnIndex(column);
				if (columnIndex >= 0 || !ignoreNotExist) {
					mValues.put(column, c.getInt(columnIndex) == 0 ? 0 : 1);
				}
			}
		}

		if (doubleColumns != null){
			for (String column : doubleColumns) {
				columnIndex = c.getColumnIndex(column);
				if (columnIndex >= 0 || !ignoreNotExist) {
					mValues.put(column, c.getDouble(columnIndex));
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	public <T> T get(String key) {
		Object value;
		if (mChangedValues.containsKey(key)) {
			value = mChangedValues.get(key);
		} else {
			value = mValues.get(key);
		}
		T result = null;
		try {
			result = (T) value;
		} catch (Exception e) {
			Log.w(LOG_TAG, "Cast to target class failed.", e);
		}

		return result;
	}

	/**
	 * put the change values into mChangeValues
	 * 
	 * @param key
	 * @param value
	 */
	protected void setString(String key, String value) {
		if (TextUtils.isEmpty(key)
				|| (COLUMNS_STR != null && !COLUMNS_STR.contains(key))) {
			throw new IllegalArgumentException(key + " is not a String column.");
		}
		if (value.equals(mValues.getAsString(key))) {
			mChangedValues.remove(key);
		} else {
			mChangedValues.put(key, value);
		}
	}

	public String getString(String key) {
		String value = null;
		if (mChangedValues.containsKey(key)) {
			value = mChangedValues.getAsString(key);
		} else {
			value = mValues.getAsString(key);
		}
		return value;
	}

	protected void setLong(String key, long value) {
		if (TextUtils.isEmpty(key)
				|| (COLUMNS_LONG != null && !COLUMNS_LONG.contains(key))) {
			throw new IllegalArgumentException(key + " is not a long column.");
		}
		if (mValues.getAsLong(key).equals(value)) {
			mChangedValues.remove(key);
		} else {
			mChangedValues.put(key, value);
		}
	}

	public long getLong(String key) {
		Long value = null;
		if (mChangedValues.containsKey(key)) {
			value = mChangedValues.getAsLong(key);
		} else {
			value = mValues.getAsLong(key);
		}

		return value == null ? -1 : value;
	}

	protected void setInt(String key, int value) {
		if (TextUtils.isEmpty(key)
				|| (COLUMNS_INT != null && !COLUMNS_INT.contains(key))) {
			throw new IllegalArgumentException(key + " is not a int column.");
		}
		_setInt(key, value);
	}

	private void _setInt(String key, int value) {
		if (mValues.getAsInteger(key).equals(value)) {
			mChangedValues.remove(key);
		} else {
			mChangedValues.put(key, value);
		}
	}

	public int getInt(String key) {
		Integer value = null;
		if (mChangedValues.containsKey(key)) {
			value = mChangedValues.getAsInteger(key);
		} else {
			value = mValues.getAsInteger(key);
		}

		return value == null ? -1 : value;
	}

	protected void setBoolean(String key, Boolean value) {
		if (TextUtils.isEmpty(key)
				|| (COLUMNS_BOOL != null && !COLUMNS_BOOL.contains(key))) {
			throw new IllegalArgumentException(key + " is not a bool column.");
		}
		_setInt(key, value ? 1 : 0);
	}

	public boolean getBoolean(String key) {
		return getInt(key) != 0;
	}

	protected void setDouble(String key, Double value) {
		if (TextUtils.isEmpty(key)
				|| (COLUMNS_DOUBLE != null && !COLUMNS_DOUBLE.contains(key))) {
			throw new IllegalArgumentException(key + " is not a double column.");
		}
		setDouble(key,value);
	}

	public boolean getDouble(String key) {
		return getDouble(key);
	}

	public int getId() {
		return getInt(_ID);
	}

	/**
	 * @return mChangedValues.size()
	 */
	public boolean needCommitChange() {
		return mChangedValues.size() > 0;
	}

	/**
	 * we can use the ChangedValues to update and insert the values into sqlite
	 * 
	 * @param cr
	 */
	public void commitChange(ContentResolver cr) {
		if (!needCommitChange()) {
			return;
		}

		final Uri uri = getUri();
		if (uri == null) {
			Uri result = cr.insert(getBaseUri(), mChangedValues);

			if (result != null) {
				setInt(_ID, (int) ContentUris.parseId(result));
			}
		} else {
			cr.update(uri, mChangedValues, null, null);
		}
		mValues.putAll(mChangedValues);
		mChangedValues.clear();
	}

	/**
	 * reload date from sqlite by c, put these values into mValues
	 * 
	 * @param cr
	 * @param cleanChange
	 */
	public void reload(ContentResolver cr, boolean cleanChange) {
		Uri uri = getUri();
		if (uri == null) {
			if (cleanChange) {
				mChangedValues.clear();
			}
			return;
		}

		Cursor c = null;
		try {
			c = cr.query(uri, null, null, null, null);
			if (c == null){
				return;
			}
			if (c.moveToFirst()) {
				reload(c, cleanChange);
			} else {
				mValues.remove(_ID);
				if (cleanChange) {
					mChangedValues.clear();
				}
			}
		} finally {
			if (c != null) {
				c.close();
			}
		}
	}

	public void reload(Cursor c, boolean cleanChange) {
		int id = getId();
		if (c == null
				|| (id > 0 && id != c.getInt(c.getColumnIndexOrThrow(_ID)))) {
			if (cleanChange) {
				mChangedValues.clear();
			}
			return;
		}

		init(c, mIgnoreNotExist);
		if (cleanChange) {
			mChangedValues.clear();
		} else {
			HashSet<String> removedKeys = new HashSet<String>();
			for (Entry<String, Object> entry : mChangedValues.valueSet()) {
				String key = entry.getKey();
				Object obj = entry.getValue();
				if (obj.equals(mValues.get(key))) {
					removedKeys.add(key);
				}
			}
			for (String key : removedKeys) {
				mChangedValues.remove(key);
			}
		}
	}

	/**
	 * delete the sql values by id
	 * 
	 * @param cr
	 */
	public void delete(ContentResolver cr) {
		final Uri uri = getUri();
		if (uri != null) {
			cr.delete(uri, null, null);
		}
	}

	public ContentValues getUncommitedValues(boolean moveToCommited) {
		ContentValues result = new ContentValues(mChangedValues);

		if (moveToCommited) {
			mValues.putAll(mChangedValues);
			mChangedValues.clear();
		}

		return result;
	}

	public ContentValues getAllValues(boolean includeId, boolean moveToCommited) {
		ContentValues result = new ContentValues(mValues);
		result.putAll(mChangedValues);
		if (moveToCommited) {
			mValues.putAll(mChangedValues);
			mChangedValues.clear();
		}
		if (!includeId) {
			result.remove(_ID);
		}
		return result;
	}

	/**
	 * 
	 * 访问资源的命名机制+ 存放资源的主机名+ 资源自身的名称，由路径表示。 base uri like
	 * content://contacts/people
	 * 
	 * @return
	 */
	abstract protected Uri getBaseUri();

	/**
	 * 
	 * @return The specified ID URI
	 */
	public Uri getUri() {
		final int id = getId();
		Uri result = null;
		if (id > 0) {
			result = ContentUris.withAppendedId(getBaseUri(), id);
		}
		return result;
	}
}
